﻿using System;
using System.IO;
using System.Linq;

namespace Gmantis.Imaging
{
    internal class PngStream : Stream
    {
        // Fields
        private byte _colorType;
        private byte[] _data;
        private byte[] _dataCrc;
        private byte[] _dataLength;
        private byte[] _hdrCrc;
        private byte[] _height;
        private byte[] _width;
        private byte[] _zlibHeader;
        private static readonly int colorIndex = 0x19;
        private static readonly int dataCrcIndex = 0;
        private static readonly int dataLengthIndex = 0x21;
        private static readonly int dataStart = 0x25;
        private static readonly int hdrCrcIndex = 0x1d;
        private static readonly int hdrStart = 12;
        private static readonly int heightIndex = 20;
        private static readonly byte[] pngEnd = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 130 };
        private static readonly byte[] pngStart = new byte[] { 
        0x89, 80, 0x4e, 0x47, 13, 10, 0x1a, 10, 0, 0, 0, 13, 0x49, 0x48, 0x44, 0x52, 
        0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 0, 0, 0, 0, 0, 0, 
        0, 0, 0, 0, 0, 0x49, 0x44, 0x41, 0x54, 120, 0xda
     };
        private static readonly int widthIndex = 0x10;
        private static readonly int zlibHeaderIndex = 0x29;

        // Methods
        public PngStream(byte[] data, byte[] zlibHeader, int width, int height, byte colorType, bool useCrc)
        {
            this._data = data;
            this._zlibHeader = zlibHeader;
            this._width = BitConverter.GetBytes(width);
            this._height = BitConverter.GetBytes(height);
            this._colorType = colorType;
            this._dataLength = BitConverter.GetBytes((int)(this._data.Length + 2));
            if (useCrc)
            {
                Crc32 crc = new Crc32();
                crc.UpdateCrc(pngStart, hdrStart, widthIndex - hdrStart);
                crc.UpdateCrc(this._width.Reverse<byte>().ToArray<byte>());
                crc.UpdateCrc(this._height.Reverse<byte>().ToArray<byte>());
                crc.UpdateCrc(pngStart, heightIndex + this._height.Length, colorIndex - (heightIndex + this._height.Length));
                crc.UpdateCrc(new byte[] { this._colorType });
                crc.UpdateCrc(pngStart, colorIndex + 1, hdrCrcIndex - (colorIndex + 1));
                this._hdrCrc = BitConverter.GetBytes(crc.Value);
                crc = new Crc32();
                if (this._zlibHeader == null)
                {
                    crc.UpdateCrc(pngStart, dataStart, pngStart.Length - dataStart);
                }
                else
                {
                    crc.UpdateCrc(pngStart, dataStart, zlibHeaderIndex - dataStart);
                    crc.UpdateCrc(this._zlibHeader);
                }
                crc.UpdateCrc(data);
                this._dataCrc = BitConverter.GetBytes(crc.Value);
            }
            else
            {
                byte[] buffer2 = new byte[4];
                this._hdrCrc = this._dataCrc = buffer2;
            }
        }

        private void BlockCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int offset, int count)
        {
            if (((srcOffset + src.Length) > dstOffset) && (srcOffset < (dstOffset + count)))
            {
                int num = Math.Max(0, dstOffset - srcOffset);
                int num2 = Math.Max(0, srcOffset - dstOffset) + offset;
                int num3 = Math.Min(Math.Min(count, src.Length - num), dst.Length - num2);
                Buffer.BlockCopy(src, num, dst, num2, num3);
            }
        }

        private static void CopyByte(byte value, byte[] array, int index)
        {
            if ((index >= 0) && (index < array.Length))
            {
                array[index] = value;
            }
        }

        private static void CopyByte(byte value, byte[] array, int srcIndex, int dstIndex)
        {
            CopyByte(value, array, srcIndex - dstIndex);
        }

        private static void CopyInt(byte[] bytes, byte[] array, int srcIndex, int dstIndex)
        {
            int index = srcIndex - dstIndex;
            CopyByte(bytes[3], array, index);
            CopyByte(bytes[2], array, index + 1);
            CopyByte(bytes[1], array, index + 2);
            CopyByte(bytes[0], array, index + 3);
        }

        public override void Flush()
        {
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            int position = (int)this.Position;
            this.BlockCopy(pngStart, 0, buffer, position, offset, count);
            this.BlockCopy(this._data, pngStart.Length, buffer, position, offset, count);
            this.BlockCopy(pngEnd, pngStart.Length + this._data.Length, buffer, position, offset, count);
            CopyInt(this._width, buffer, widthIndex, position);
            CopyInt(this._height, buffer, heightIndex, position);
            CopyByte(this._colorType, buffer, colorIndex, position);
            CopyInt(this._hdrCrc, buffer, hdrCrcIndex, position);
            CopyInt(this._dataLength, buffer, dataLengthIndex, position);
            if (this._zlibHeader != null)
            {
                CopyByte(this._zlibHeader[0], buffer, zlibHeaderIndex, position);
                CopyByte(this._zlibHeader[1], buffer, zlibHeaderIndex + 1, position);
            }
            CopyInt(this._dataCrc, buffer, (dataCrcIndex + pngStart.Length) + this._data.Length, position);
            this.Position += count;
            return count;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            long num = 0L;
            switch (origin)
            {
                case SeekOrigin.Begin:
                    num = offset;
                    break;

                case SeekOrigin.Current:
                    num += offset;
                    break;

                case SeekOrigin.End:
                    num = this.Length + offset;
                    break;
            }
            long position = this.Position;
            this.Position = Math.Max(0L, Math.Min(num, this.Length));
            return (num - position);
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        // Properties
        public override bool CanRead
        {
            get
            {
                return true;
            }
        }

        public override bool CanSeek
        {
            get
            {
                return true;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return false;
            }
        }

        public override long Length
        {
            get
            {
                return (long)((pngStart.Length + this._data.Length) + pngEnd.Length);
            }
        }

        public override long Position { get; set; }
    }
}
